﻿//==============================================================================  
//Copyright (C) 2012-2015 9UN.ORG. All rights reserved. 
//GUID：46172595-6b69-4526-b9da-426d663f5b05
//CLR Version: 4.0.30319.18033
//Code Author：Kevin Wang
//Contact：Email(Admin@9un.org),QQ(265382 or 74344)
//Filename：MediaLibraryFactory
//Namespace：RuanRang.Media.Core
//Functions：MediaLibraryFactory  
//Created by Kevin Wang at 2013/2/2 23:05:09 http://blog.9un.org
//============================================================================== 
#region

using System;
using System.IO;
using RuanRang.Media.Core.Internal;
using RuanRang.Media.Core.Io;
using RuanRang.Media.Core.Media;
using log4net;

#endregion

namespace RuanRang.Media.Core
{
    public sealed class VlcMediaPlayer : BaseMediaPlayer
    {
        /// <summary>
        ///     The logger
        /// </summary>
        private static readonly ILog Logger = LogManager.GetLogger(typeof(VlcMediaPlayer));

        // these will be set from SetMediaInput methods: 
        /// <summary>
        ///     The _current media internal preprepared
        /// </summary>
        private BaseMedia _currentPrepreparedMedia;

        /// <summary>
        ///     The _next media
        /// </summary>
        private MediaInput _nextMedia;

        /// <summary>
        ///     The _next media internal preprepared
        /// </summary>
        private BaseMedia _nextPrepreparedMedia;

        /// <summary>
        ///     The _waiting required state timeout
        /// </summary>
        /// <summary>
        ///     Initializes a new instance of the <see cref="VlcMediaPlayer" /> class.
        /// </summary>
        /// <param name="playerOutput">The player output.</param>
        /// <param name="libVlcFactory">The lib VLC factory.</param>
        /// <exception cref="System.ArgumentNullException">
        ///     playerOutput
        ///     or
        ///     internalObjectsFactory
        /// </exception>
        /// <exception cref="System.ArgumentException">Window property of PlayerOutput should be of class DoubleWindowBase.;playerOutput</exception>
        internal VlcMediaPlayer(PlayerOutput playerOutput, ILibVlcFactory libVlcFactory)
            : base(libVlcFactory)
        {
            if (playerOutput == null)
            {
                throw new ArgumentNullException("playerOutput");
            }
            if (playerOutput.Window != null && !(playerOutput.Window is DoubleWindowBase))
            {
                const string errmsg = "Window property of PlayerOutput should be of class DoubleWindowBase.";
#if DEBUG

                Logger.Error(errmsg);
#endif
                //NOTE: it is not realy neccessary for VlcSinglePlayer - should we create simpler Window class?
                throw new ArgumentException(errmsg);
            }
            if (libVlcFactory == null)
            {
                throw new ArgumentNullException("libVlcFactory");
            }
            //
            PlayerOutput = playerOutput;
            LibVlcFactoryInstance = libVlcFactory;
        }

        /// <summary>
        ///     Sets the media input.
        /// </summary>
        /// <param name="mediaInput">The media input.</param>
        /// <exception cref="System.ArgumentNullException">mediaInput</exception>
        public override void SetMediaInput(MediaInput mediaInput)
        {
            if (mediaInput == null)
            {
                throw new ArgumentNullException("mediaInput");
            }
            CurrentMediaInput = mediaInput;
            // precreating of libvlc media
            if (CurrentMedia != _currentPrepreparedMedia)
            {
                if (_currentPrepreparedMedia != null)
                {
                    _currentPrepreparedMedia.Dispose();
                    _currentPrepreparedMedia = null;
                }
            }
            _currentPrepreparedMedia = LibVlcFactoryInstance.CreateLibVlcMedia(CurrentMediaInput);
            _currentPrepreparedMedia.SetOutput(PlayerOutput);
        }

        /// <summary>
        ///     Initializes previously preparsed media to play.
        /// </summary>
        /// <param name="mediaInput">The media input.</param>
        /// <exception cref="System.ArgumentNullException">preparsedMedia</exception>
        public override void SetMediaInput(BaseMedia mediaInput)
        {
            if (mediaInput == null)
            {
                throw new ArgumentNullException("mediaInput");
            }
            //
            CurrentMediaInput = mediaInput.MediaInput;
            CurrentMedia = mediaInput;
            // precreating of libvlc media
            if (CurrentMedia != null && CurrentMedia.Handle != _currentPrepreparedMedia.Handle)
            {
                if (_currentPrepreparedMedia != null)
                {
                    _currentPrepreparedMedia.Dispose();
                    _currentPrepreparedMedia = null;
                }
            }
            mediaInput.SetOutput(PlayerOutput);
            //
            _currentPrepreparedMedia = mediaInput;
            //_disposeCurrentMediaPreprepared = false;
        }

        /// <summary>
        ///     Specifies next media input.
        /// </summary>
        public override void SetNextMediaInput(MediaInput mediaInput)
        {
            //
            if (mediaInput == null)
            {
                throw new ArgumentNullException("mediaInput");
            }
            //
            _nextMedia = mediaInput;
            // precreating of libvlc media
            if (CurrentMedia != null && CurrentMedia.Handle != _currentPrepreparedMedia.Handle)
            {
                if (_nextPrepreparedMedia != null)
                {
                    _nextPrepreparedMedia.Dispose();
                    _nextPrepreparedMedia = null;
                }
            }
            _nextPrepreparedMedia = LibVlcFactoryInstance.CreateLibVlcMedia(_nextMedia);
            _nextPrepreparedMedia.SetOutput(PlayerOutput);
            //_disposeNextMediaPreprepared = true;
        }

        /// <summary>
        ///     Initializes previously preparsed next media to play.
        /// </summary>
        /// <param name="mediaInput">The media input.</param>
        /// <exception cref="System.ArgumentNullException">mediaInput</exception>
        public override void SetNextMediaInput(BaseMedia mediaInput)
        {
            //
            if (mediaInput == null)
            {
                throw new ArgumentNullException("mediaInput");
            }
            //
            _nextMedia = mediaInput.MediaInput;
            // precreating of libvlc media
            if (CurrentMedia != null && CurrentMedia.Handle != _currentPrepreparedMedia.Handle)
            {
                if (_nextPrepreparedMedia != null)
                {
                    _nextPrepreparedMedia.Dispose();
                    _nextPrepreparedMedia = null;
                }
            }
            mediaInput.SetOutput(PlayerOutput);
            //
            _nextPrepreparedMedia = mediaInput;
            // _disposeNextMediaPreprepared = true;
        }

        /// <summary>
        ///     Starts playing of media which was initialized using
        ///     <see>
        ///         <cref>SetMediaInput</cref>
        ///     </see>
        ///     method.
        ///     If some media is playing now it will be simply restarted.
        /// </summary>
        public override void Play()
        {
            if (CurrentMediaInput == null)
            {
                throw new MediaPlayerException("Current media is null.");
            }
            if (PlayerOutput == null)
            {
                throw new MediaPlayerException("Player output is null.");
            }
            //
            if (CurrentMedia != null)
            {
                if (State == VlcPlayerState.Paused)
                {
                    Resume();
                    return;
                }
                if (State == VlcPlayerState.Playing)
                {
                    Position = 0f;
                    return;
                }
                Stop();
            }
            // Verify currentMedia
            if (CurrentMediaInput.Type == MediaInputType.File)
            {
                if (!File.Exists(CurrentMediaInput.Source))
                {
                    throw new FileNotFoundException("File of media specified was not found.", CurrentMediaInput.Source);
                }
            }
            if (_currentPrepreparedMedia == null)
            {
                _currentPrepreparedMedia = LibVlcFactoryInstance.CreateLibVlcMedia(CurrentMediaInput);
                _currentPrepreparedMedia.SetOutput(PlayerOutput);
            }
            // 
            CurrentMedia = _currentPrepreparedMedia;
            //CurrentMedia.StateChanged += (sender, arg) =>
            //    {
            //        var state = (VlcMediaState)arg.Data;
            //        State = state;
            //    };
            _currentPrepreparedMedia = null;
            //
            SetMedia(CurrentMedia);
            SetPlayWindow(PlayerOutput);
            base.Play();
        }

        /// <summary>
        ///     Plays the specified media input.
        /// </summary>
        /// <param name="mediaInput">The media input.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        public override void Play(MediaInput mediaInput)
        {
            SetMediaInput(mediaInput);
            Play();
        }

        /// <summary>
        ///     Pauses this instance.
        /// </summary>
        /// <exception cref="MediaPlayerException">Current media is not loaded.</exception>
        public override void Pause()
        {
            if (State == VlcPlayerState.Paused || State == VlcPlayerState.Ended)
            {
                return;
            }
            if (State != VlcPlayerState.Playing)
            {
                throw new MediaPlayerException("Unexpected media state, must be playing.");
            }
            base.Pause();
        }

        /// <summary>
        ///     Resumes this instance.
        /// </summary>
        /// <exception cref="MediaPlayerException">Current media is not loaded.</exception>
        public override void Resume()
        {
            if (State == VlcPlayerState.Playing || State == VlcPlayerState.Ended)
            {
                return;
            }
            if (State != VlcPlayerState.Paused)
            {
                throw new MediaPlayerException("Unexpected media state, must be paused.");
            }
            base.Pause();
        }

        /// <summary>
        ///     Stops this instance.
        /// </summary>
        public override void Stop()
        {
            if (CurrentMedia == null) return;
            //调用libvlc_media_player_stop经常会导致程序挂起
            //  后面发现原来是调用libvlc_media_player_stop时发生死锁了。
            //通常出现libvlc_media_player_stop函数挂起时，我们都设置了回调函数，包括display、stop event之类的回调函数。
            //由于回调函数和我们调用的libvlc_media_player_stop函数通常不在同一个线程上。由于libvlc_media_player_stop函数中需要互斥锁资源，如果在回调函数中调用了libvlc中需要互斥锁资源的接口时，就会出现死锁的情况。
            //那么那些接口是需要互斥锁资源的呢？
            //其实，我们不用管这么多，只需要保持在回调函数中不调用libvlc的其他接口就好了。或者在调用接口时做好和其他线程的同步处理，让多个线程进行接口调用时不要出现资源竞争就好了。
            //当然在进行同步的时候要优先采用无锁的机制，保证效率的同时又避免引入潜在的死锁。
            base.Stop();
            CurrentMedia.Dispose();
            CurrentMedia = null;
        }

        /// <summary>
        ///     Internal.
        /// </summary>
        /// <exception cref="MediaPlayerException">Next media is not selected.</exception>
        /// <exception cref="System.IO.FileNotFoundException">File of media specified was not found.</exception>
        public override void Backward()
        {
            if (_nextMedia == null)
            {
                throw new MediaPlayerException("Next media is not selected.");
            }
            // Verify nexyMedia
            if (_nextMedia.Type == MediaInputType.File)
            {
                if (!File.Exists(_nextMedia.Source))
                {
                    throw new FileNotFoundException("File of media specified was not found.", _nextMedia.Source);
                }
            }
            // create nextMediaInternal and start playing 
            var nextMediaInternal = _nextPrepreparedMedia;
            _nextPrepreparedMedia = null;
            nextMediaInternal.SetOutput(PlayerOutput);
            SetMedia(nextMediaInternal);
            SetPlayWindow(PlayerOutput);
            if (CurrentMedia != null) // && _disposeCurrentMedia)
            {
                CurrentMedia.Dispose();
                CurrentMedia = null;
            }
            //
            CurrentMedia = nextMediaInternal;
            //_disposeCurrentMedia = _disposeNextMediaPreprepared;
            //
            //
            base.Play();
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                //free managed resources
            }
            // free unmanaged resources
            if (CurrentMedia != null)
            {
                CurrentMedia.Dispose();
                CurrentMedia = null;
            }
            if (_currentPrepreparedMedia != null)
            {
                _currentPrepreparedMedia.Dispose();
                _currentPrepreparedMedia = null;
            }
            if (_nextPrepreparedMedia != null)
            {
                _nextPrepreparedMedia.Dispose();
                _nextPrepreparedMedia = null;
            }
            base.Dispose(true);
        }
    }
}